01 |
您所在的位置:网站首页 › grpc createchannel › 01 |
2 Protocol Buffers编码介绍
2.1 Protocol Buffers编码格式
Protocol Buffers编码提供了一种灵活、高效、自动序列化结构数据的机制。Protocol Buffers与XML、JSON编码类似,不同之处在于Protocol Buffers是一种二进制编码,性能更高。 表2-1对比了Protocol Buffers和对应的JSON编码格式。 表2-1 Protocol Buffers和对应的JSON编码格式示例 Protocol Buffers编码 对应的JSON编码 { 1:“H3C” 2:“H3C” 3:“H3C device_test” 4:“Syslog/LogBuffer” 5:"notification": { "Syslog": { "LogBuffer": { "BufferSize": 512, "BufferSizeLimit": 1024, "DroppedLogsCount": 0, "LogsCount": 100, "LogsCountPerSeverity": { "Alert": 0, "Critical": 1, "Debug": 0, "Emergency": 0, "Error": 3, "Informational": 80, "Notice": 15, "Warning": 1 }, "OverwrittenLogsCount": 0, "State": "enable" } }, "Timestamp": "1527206160022" } } { "producerName": "H3C", "deviceName": "H3C", "deviceModel": "H3C device_test", "sensorPath": "Syslog/LogBuffer", "jsonData": { "notification": { "Syslog": { "LogBuffer": { "BufferSize": 512, "BufferSizeLimit": 1024, "DroppedLogsCount": 0, "LogsCount": 100, "LogsCountPerSeverity": { "Alert": 0, "Critical": 1, "Debug": 0, "Emergency": 0, "Error": 3, "Informational": 80, "Notice": 15, "Warning": 1 }, "OverwrittenLogsCount": 0, "State": "enable" } }, "Timestamp": "1527206160022" } } } 2.2 proto文件介绍 Protocol Buffers编码通过proto文件描述数据结构,用户可以利用Protoc等工具软件根据proto文件自动生成其他编程语言(例如Java、C++)代码,然后基于这些生成的代码进行二次开发,以实现gRPC设备对接。 H3C为Dial-in模式和Dial-out模式分别提供了proto文件。 2.2.1 Dial-in模式的proto文件 1. 公共proto文件Dial-in模式的公共proto文件包括: · grpc_service.proto:定义了Dial-in模式下的公共RPC方法。 · gnmi.proto:定义了gNMI类操作的公共RPC方法。 · gnmi_ext.proto:定义了gnmi.proto文件所需的扩展消息结构。 其中gnmi.proto和gnmi_ext.proto文件由Google发布,下载地址请参见“2.2.3 获取proto文件的方法”。 grpc_service.proto文件由H3C提供,其内容和含义如下: syntax = "proto2"; package grpc_service; message GetJsonReply { //Get方法应答结果 required string result = 1; } message SubscribeReply { //订阅结果 required string result = 1; } message ConfigReply { //配置结果 required string result = 1; } message ReportEvent { //订阅事件结果定义 required string token_id = 1; //登录token_id required string stream_name = 2; //订阅的事件流名称 required string event_name = 3; //订阅的事件名 required string json_text = 4; //订阅结果json字符串 } message GetReportRequest{ //获取事件订阅结果请求 required string token_id = 1; //登录成功后的token_id } message LoginRequest { //登录请求参数定义 required string user_name = 1; //登录请求用户名 required string password = 2; //登录请求密码 } message LoginReply { //登录请求应答定义 required string token_id = 1; //登录成功后返回的token_id } message LogoutRequest { //退出登录请求参数定义 required string token_id = 1; //token_id } message LogoutReply { //退出登录返回结果定义 required string result = 1; //退出登录结果 } message SubscribeRequest { //定义事件流名称 required string stream_name = 1; } message CliConfigArgs { //向设备下发配置命令,并指定命令行参数 required int64 ReqId = 1; //配置命令请求ID required string cli = 2; //配置命令 } message CliConfigReply { //设备返回配置命令行执行的结果 required int64 ResReqId = 1; //返回配置命令请求ID,与CliConfigArgs相对应 required string output = 2; //返回配置命令执行输出 required string errors = 3; //标记配置命令执行结果 } message DisplayCmdArgs { //向设备下发display命令,并指定命令行参数 required int64 ReqId = 1; //display命令请求ID required string cli = 2; //display命令 } message DisplayCmdReply { //设备返回display命令行执行的结果 required int64 ResReqId =1; //display命令请求ID,与DisplayCmdArgs相对应 required string output = 2; //返回display命令执行输出 required string errors = 3; //标记display命令执行结果 } service GrpcService { //定义gRPC方法 rpc Login (LoginRequest) returns (LoginReply) {} //登录方法 rpc Logout (LogoutRequest) returns (LogoutReply) {} //退出登录方法 rpc SubscribeByStreamName (SubscribeRequest) returns (SubscribeReply) {} //订阅事件流 rpc GetEventReport (GetReportRequest) returns (stream ReportEvent) {} //获取事件结果 rpc CliConfig (CliConfigArgs) returns (stream CliConfigReply) {} //gRPC支持通过命令行下发配置命令,并返回执行结果 rpc DisplayCmdTextOutput(DisplayCmdArgs) returns(stream DisplayCmdReply) {} //gRPC支持通过命令行下发display命令,并返回查询结果 } 2. 业务模块proto文件Dial-in模式支持Device、Ifmgr、IPFW、LLDP、Syslog等业务模块proto文件。 以Device.proto文件为例,该文件定义了Device模块数据的RPC方法,其内容和含义如下: syntax = "proto2"; import "grpc_service.proto"; package device; message DeviceBase { //获取设备基本信息结构定义 optional string HostName = 1; //设备的名称 optional string HostOid = 2; //sysoid optional uint32 MaxChassisNum = 3; //最大框数 optional uint32 MaxSlotNum = 4; //最大slot数 optional string HostDescription = 5; //设备描述信息 } message DevicePhysicalEntities { //设备物理实体信息 message Entity { optional uint32 PhysicalIndex = 1; //实体索引 optional string VendorType = 2; //vendor类型 optional uint32 EntityClass = 3;//实体类型 optional string SoftwareRev = 4; //软件版本 optional string SerialNumber = 5; //序列号 optional string Model = 6; //模式 } repeated Entity entity = 1; } service DeviceService { //定义的RPC方法 rpc GetJsonDeviceBase(DeviceBase) returns (grpc_service.GetJsonReply) {} //获取设备基本信息 rpc GetJsonDevicePhysicalEntities(DevicePhysicalEntities) returns (grpc_service.GetJsonReply) {} //获取设备实体信息 } 2.2.2 Dial-out模式的proto文件Dial-out模式的公共proto文件包括: · grpc_dialout.proto:定义了Dial-out模式下普通订阅的公共RPC方法。 · dial_out.proto:定义了Dial-out模式下gNMI模式订阅的报文格式。 · gnmi.proto:定义了gNMI模式订阅的公共RPC方法。 · gnmi_ext.proto:定义了gnmi.proto文件所需的扩展消息结构。 其中dial_out.proto文件由SONIC发布,gnmi.proto和gnmi_ext.proto文件由Google发布,下载地址请参见“2.2.3 获取proto文件的方法”。 grpc_dialout.proto文件由H3C提供,其内容和含义如下: syntax = "proto2"; package grpc_dialout; message DeviceInfo{ //推送的设备信息 required string producerName = 1; //厂商名 required string deviceName = 2; //设备的名称 required string deviceModel = 3; //设备型号 } message DialoutMsg{ //推送的消息格式描述 required DeviceInfo deviceMsg = 1; //DeviceInfo所描述的设备信息 required string sensorPath = 2; //采样路径,对应netconf表的xpath路径 required string jsonData = 3; //采样结果数据(JSON格式字符串) } message DialoutResponse{ //采集器(gRPC服务器)返回信息,预留(暂不处理返回值,可填充任意值) required string response = 1; } service GRPCDialout { //推送方法 rpc Dialout(stream DialoutMsg) returns (DialoutResponse); } 2.2.3 获取proto文件的方法可联系H3C技术支持提供proto文件。 gnmi.proto和gnmi_ext.proto文件可从以下网站下载: · https://github.com/openconfig/gnmi/tree/master/proto/gnmi/gnmi.proto · https://github.com/openconfig/gnmi/tree/master/proto/gnmi_ext/gnmi_ext.proto dial-out.proto文件可从以下网站下载: · https://github.com/Azure/sonic-telemetry/blob/master/proto/dial_out.proto 2.3 gRPC对接软件二次开发举例本举例开发的软件用于实现: · 采集器获取设备数据(Dial-in模式下的Get操作、gNMI Capabilities操作、gNMI Get操作、gNMI Subscribe操作或Dial-out模式) · 采集器向设备下发配置(Dial-in模式下的gNMI Set操作或CLI操作) 开发环境为Linux,编程语言以C++为例。 2.3.1 准备工作(1) 获取proto文件: ¡ Dial-in模式下的Get操作:需要grpc_service.proto文件和具体业务模块对应的proto文件。 ¡ Dial-in模式下的gNMI类操作:需要grpc_service.proto、gnmi.proto和gnmi_ext.proto文件。 ¡ Dial-in模式下的CLI操作:需要grpc_service.proto文件。 ¡ Dial-out模式下的普通订阅:需要grpc_dialout.proto文件。 ¡ Dial-out模式下的gNMI模式订阅:需要dial_out.proto、gnmi.proto和gnmi_ext.proto文件。 (2) 获取处理proto文件的工具软件protoc。 下载地址:https://github.com/google/protobuf/releases (3) 获取对应开发语言的protobuf插件,例如C++插件protobuf-cpp。 下载地址:https://github.com/google/protobuf/releases 2.3.2 生成代码生成proto文件对应的C++代码。 1. Dial-in模式将需要的proto文件收集到当前目录下,例如grpc_service.proto和BufferMonitor.proto。 $protoc --plugin=./grpc_cpp_plugin --grpc_out=. --cpp_out=. *.proto 2. Dial-out模式将grpc_dialout.proto文件收集到当前目录下。 $ protoc --plugin=./grpc_cpp_plugin --grpc_out=. --cpp_out=. *.proto 2.3.3 开发代码(Dial-in模式)对于Dial-in模式,主要是实现gRPC客户端代码(采集器上使用)。 由于生成的proto代码已经封装好了对应的服务类,只要在编写的客户端代码中调用其RPC方法,客户端就能够向设备(gRPC服务器)发起对应的RPC请求。 客户端代码主要包括以下3部分: · 进行登录操作,获取token_id。 · 为要发起的RPC方法准备参数,用proto生成的服务类发起RPC调用并解析返回结果。 · 退出登录。 1. Get操作以调用GrpcService和BufferMonitorService服务类为例,编码步骤如下: (1) 编写一个GrpcServiceTest类。 在这个类中使用由grpc_service.proto生成的GrpcService::Stub类,通过grpc_service.proto自动生成的Login和Logout方法分别完成登录和退出。 class GrpcServiceTest { public: /* 构造函数 */ GrpcServiceTest(std::shared_ptr channel): GrpcServiceStub(GrpcService::NewStub(channel)) {}
/* 成员函数 */ int Login(const std::string& username, const std::string& password); void Logout(); void listen(); Status listen(const std::string& command);
/* 成员变量 */ std::string token;
private: std::unique_ptr GrpcServiceStub; //使用grpc_service.proto生成的GrpcService::Stub类 }; (2) 实现自定义的Login方法。 通过用户输入的用户名,密码调用GrpcService::Stub类的Login方法完成登录。 int GrpcServiceTest::Login(const std::string& username, const std::string& password) { LoginRequest request; //设置用户名密码 request.set_user_name(username); request.set_password(password);
LoginReply reply; ClientContext context;
//调用登录方法 Status status = GrpcServiceStub->Login(&context, request, &reply); if (status.ok()) { std::cout add_elem(); pathelem03->set_name("Neighbor"); (*pathelem03->mutable_key())["IfName"] = "xxx";
subscribeList->set_mode(::gnmi::SubscriptionList_Mode_ONCE); subscribeList->set_encoding(::gnmi::JSON); }
void gnmi_client::FillSubscribeRequestByPool(gnmi::SubscribeRequest &request) { auto subscribeList = request.mutable_subscribe();
auto prefix = subscribeList->mutable_prefix(); auto pathelem01 = prefix->add_elem(); pathelem01->set_name("Device");
auto subscribe = subscribeList->add_subscription();
auto path = subscribe->mutable_path(); auto pathelem02 = path->add_elem(); pathelem02->set_name("CPUs"); auto pathelem03 = path->add_elem(); pathelem03->set_name("CPU"); auto pathelem04 = path->add_elem(); pathelem04->set_name("CPUUsage");
subscribeList->set_mode(::gnmi::SubscriptionList_Mode_POLL); subscribeList->set_encoding(::gnmi::JSON); }
void gnmi_client::FillSubscribeRequestByStream(gnmi::SubscribeRequest &request) { auto subscribeList = request.mutable_subscribe();
auto prefix = subscribeList->mutable_prefix(); auto pathelem01 = prefix->add_elem(); pathelem01->set_name("Diagnostic");
auto subscribe = subscribeList->add_subscription();
auto path = subscribe->mutable_path(); auto pathelem02 = path->add_elem(); pathelem02->set_name("CPUEvent"); auto pathelem03 = path->add_elem(); pathelem03->set_name("CPU"); (*pathelem03->mutable_key())["Chassis#condition"] = "equal:1"; subscribe->set_mode(::gnmi::ON_CHANGE); subscribe->set_sample_interval(1000); subscribe->set_suppress_redundant(false); subscribe->set_heartbeat_interval(1000);
subscribeList->set_mode(::gnmi::SubscriptionList_Mode_STREAM); subscribeList->set_encoding(::gnmi::JSON); }
void gnmi_client::FillSubscribeAlias(gnmi::SubscribeRequest &request) { auto aliases = request.mutable_aliases(); auto alias = aliases->add_alias();
auto path = alias->mutable_path(); auto pathelem01 = path->add_elem(); pathelem01->set_name("Device"); auto pathelem02 = path->add_elem(); pathelem02->set_name("CPUs"); auto pathelem03 = path->add_elem(); pathelem03->set_name("CPU"); auto pathelem04 = path->add_elem(); pathelem04->set_name("CPUUsage");
alias->set_alias("#cpu_usage"); } (6) 调用Logout退出登录。 与Get操作相同。 6. CLI操作(1) 编写一个GrpcServiceTest类。 与Get操作相同。 (2) 实现自定义的Login方法。 与Get操作相同。 (3) 发起对设备的RPC方法请求。 这里使用grpc_service.proto文件中的CliConfig方法: rpc CliConfig (CliConfigArgs) returns (stream CliConfigReply) {} (4) 使用GrpcServiceTest类封装发起的RPC请求。 见Get操作中的GrpcServiceTest类。 (5) 实现自定义的方法以支持CliConfig操作。 // make a thread to listen the sever and get message Status GrpcServiceTest::listen(const std::string& command) { CliConfigArgs reportRequest; ClientContext context; CliConfigReply reportedEvent; std::string key = "token_id"; std::string value = token; context.AddMetadata(key, value); /* add token to request */ reportRequest.set_reqid(12345678); reportRequest.set_cli(command);
std::unique_ptr< ClientReader< CliConfigReply>> reader(mStub->CliConfig(&context, reportRequest));
std::string streamName; std::string output; int64 resreqid;
std::cout add_elem(); pathelem03->set_name("Entity"); auto pathelem04 = path1->add_elem(); pathelem04->set_name("CpuUsage"); (*pathelem04->mutable_key())["#match"] = "more:50"; · valuetype(属性值类型) 获取接口数据时,如果IfIndex、vrfindex列的值为数字,设备无法识别该值为名称类型还是索引类型。此时,用户可以使用valuetype指定该值的类型。valuetype取值为: 属性 说明 name 值为名称类型 index 值为索引类型 auto 设备先按名称类型进行匹配,如果没有匹配到任何信息,再按照索引类型进行匹配 如果不指定valuetype的属性,缺省使用auto
如下示例中,IfIndex属性值为1,属性值类型为index: auto prefix = request.mutable_prefix(); auto pathelem01 = prefix->add_elem(); pathelem01->set_name("VLAN");
auto path1 = request.add_path(); auto pathelem02 = path1->add_elem(); pathelem02->set_name("TrunkInterfaces"); auto pathelem03 = path1->add_elem(); pathelem03->set_name("Interface"); (*pathelem03->mutable_key())["IfIndex"] = "1"; (*pathelem03->mutable_key())["IfIndex#valuetype"] = "index"; · count 该属性用于获取指定数量的数据项,例如运行状态、运行配置信息。 如下为一个携带了count属性的示例: auto prefix = request.mutable_prefix(); auto pathelem01 = prefix->add_elem(); pathelem01->set_name("Syslog");
auto path1 = request.add_path(); auto pathelem02 = path1->add_elem(); pathelem02->set_name("Logs"); (*pathelem03->mutable_key())["count"] = "5"; auto pathelem03 = path1->add_elem(); pathelem03->set_name("Log"); (*pathelem03->mutable_key())["Index"] = "10"; · 值过滤 在Get操作中,对表中的列直接指定值,设备将对这些值进行严格匹配。如果指定了多个列的值,则返回同时符合这几个条件的数据。 如下为匹配IfIndex=1的示例: auto prefix = request.mutable_prefix(); auto pathelem01 = prefix->add_elem(); pathelem01->set_name("VLAN");
auto path1 = request.add_path(); auto pathelem02 = path1->add_elem(); pathelem02->set_name("TrunkInterfaces"); auto pathelem03 = path1->add_elem(); pathelem03->set_name("Interface"); (*pathelem03->mutable_key())["IfIndex"] = "1"; 3. gNMI Set操作支持的属性gNMI Set支持的属性如下: · incremental 该属性作用于集合性质的列,例如vlan permitlist列表。XML请求中有增量下发选项时,最终执行结果不影响本列原有的数据。 例如,下发一个接口的VLAN配置,使用增量下发,262接口原有Untagged VLAN列表为12~15,下发后为1~10,12~15。请求如下: Path *path01 = request.mutable_prefix(); PathElem *pathelem01 = path01->add_elem(); pathelem01->set_name("VLAN"); PathElem *pathelem02 = path01->add_elem(); pathelem02->set_name("HybridInterfaces");
Update *Update01 = request.add_update(); Path *path02 = Update01->mutable_path(); PathElem *pathelem03 = path02->add_elem(); pathelem03->set_name("Interface"); (*pathelem03->mutable_key())["IfName"] = "262"; (*pathelem03->mutable_key())["UntaggedVlanList#incremental"] = "true"; (*pathelem03->mutable_key())["UntaggedVlanList"] = "1-10"; · valuetype 配置接口数据时,如果IfIndex和vrfindex列的值为数字,设备无法识别该值为名称类型还是索引类型。此时,用户可以使用valuetype指定该值的类型。valuetype取值为: 属性 说明 name 值为名称类型 index 值为索引类型 auto 设备先按名称类型进行匹配,如果没有匹配到任何信息,再按照索引类型进行匹配 如果不指定valuetype的属性,缺省使用auto
下面以IfIndex列值为index类型,值为1为例: Path *path01 = request.mutable_prefix(); PathElem *pathelem01 = path01->add_elem(); pathelem01->set_name("VLAN"); PathElem *pathelem02 = path01->add_elem(); pathelem02->set_name("TrunkInterfaces");
/* SetRequest->delete */ Path *path02 = request.add_delete_(); PathElem *pathelem04 = path02->add_elem(); pathelem04->set_name("Interface "); (*pathelem04->mutable_key())["IfIndex"] = "1"; (*pathelem04->mutable_key())["IfIndex#valuetype"] = "index"; 4. gNMI Subscribe操作支持的属性gNMI Subscribe支持的属性如下: · gNMI Get操作支持的所有属性,请参见“gNMI Get操作支持的属性” 这些属性可用于周期式上报的数据表的订阅。 · condition(数据推送条件) 该属性用于事件表的订阅,可选择的属性值包括: ¡ more:大于 ¡ less:小于 ¡ notLess:不小于 ¡ notMore:不大于 ¡ equal:等于 ¡ notEqual:不等于 ¡ include:包含 ¡ exclude:不包含 ¡ startWith:开始于 ¡ endWith:结束于 如下示例用于监控LLDP事件: auto prefix = subscribeList->mutable_prefix(); auto pathelem01 = prefix->add_elem(); pathelem01->set_name("LLDP");
auto subscribe = subscribeList->add_subscription(); auto path = subscribe->mutable_path(); auto pathelem02 = path->add_elem(); pathelem02->set_name("NeighborEvent"); auto pathelem03 = path->add_elem(); pathelem03->set_name("Neighbor"); (*pathelem03->mutable_key())["IfName"] = "Ten-GigabitEthernet1/0/1"; (*pathelem03->mutable_key())["IfName#condition"] = "equal"; · match(条件匹配) 该属性用于数据表的订阅,有以下两种使用方法: ¡ 与固定值进行条件匹配:支持的匹配命令如表2-3所示。 ¡ 与其他列数据进行条件匹配(仅触发式上报的数据表支持)。可选择的属性值包括: - refMore:大于 - refLess:小于 - refNotLess:不小于 - refNotMore:不大于 - refEqual:等于 - refNotEqual:不等于 下面以current-usage列值大于alarm-threshold列值触发条件为例: auto subscribe = subscribeList->add_subscription(); auto path = subscribe->mutable_path(); auto pathelem02 = path->add_elem(); pathelem02->set_name("queue"); auto pathelem03 = path->add_elem(); pathelem03->set_name("state"); (*pathelem03->mutable_key())["current-usage#match"] = "refMore:alarm-threshold"; · select 该属性用于在触发式上报的数据表的订阅中选择列,表示仅订阅指定的列数据。 下面以订阅current-usage和available-usage为例: auto subscribe = subscribeList->add_subscription(); auto path = subscribe->mutable_path(); auto pathelem02 = path->add_elem(); pathelem02->set_name("queue"); auto pathelem03 = path->add_elem(); pathelem03->set_name("state"); (*pathelem03->mutable_key())["current-usage#select"] = ""; (*pathelem03->mutable_key())["available-usage#select"] = ""; gNMI类操作对象包括数据表和事件表: · 数据表有两种类型:周期式上报和触发式上报。 · 事件表均为触发式上报。
|
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |